package mithrilwings.game;

import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.input.MouseInput;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.Vector3f;
import com.jme3.network.Client;
import com.jme3.network.Message;
import com.jme3.network.MessageListener;
import com.jme3.network.Network;
import com.jme3.network.serializing.Serializer;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Sphere;
import com.jme3.system.AppSettings;
import com.jme3.water.SimpleWaterProcessor;
import fifis.game.FGame;
import fifis.game.common.Common;
import fifis.mvc.controller.FController;
import fifis.mvc.model.FModel;
import fifis.mvc.view.FView;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.StringTokenizer;
import mithrilwings.constants.Constants;
import mithrilwings.constants.MithrilMessage;
import mithrilwings.ship.controller.ShipController;
import mithrilwings.scenario.controller.MithrilWingsScenarioController;
import org.apache.log4j.Logger;

/**
 * @author porfiriopartida
 */
public class MainMithrilWings extends FGame {

    final static protected Logger log4j = Logger.getLogger(MainMithrilWings.class);// turning off jme log
//    MithrilWingsClient client;
    public static ShipController me = null;

    public MainMithrilWings() {
        //true to remove mouse flyCam behavior
        super(false);

//        settings = new AppSettings(true);
//        settings.setRenderer(AppSettings.LWJGL_OPENGL_ANY);
//        settings.setFrameRate(30);
//        setSettings(settings);
    }

    public static void main(String[] args) {
        java.util.logging.Logger.getLogger("").setLevel(java.util.logging.Level.WARNING);
//        PropertyConfigurator.configure(Constants.LOG4J_PROPERTIES);
        log4j.info("\n\n================================================\n=                    LAUNCH                    =\n================================================\n");
        try {
            MainMithrilWings app = new MainMithrilWings();
            AppSettings settings = new AppSettings(true); //app.settings;
            settings.setRenderer(AppSettings.LWJGL_OPENGL_ANY);
            settings.setFrequency(60);
            settings.setFrameRate(20);
            app.setSettings(settings);
            app.setShowSettings(false);
            app.start();
        } catch (Exception ex) {
            javax.swing.JOptionPane.showMessageDialog(null, "Application crashed, please send the log to the administrator.", ex.getMessage(), javax.swing.JOptionPane.ERROR_MESSAGE);
            log4j.fatal("Application crashed", ex);
        }
    }
    MithrilWingsScenarioController scenario;
//    ShipController ship2;
//    MithrilWingsLogic logic;
    Node start;
    Client myClient = null;

    @Override
    public void simpleInitFApp() {
        log4j.info("Initializing Application");
        logic = new MithrilWingsLogic();
        Common.getInstance().getBulletAppState().getPhysicsSpace().addCollisionListener(logic);
        ShipController.initMaterials();
        addMouseInput("click", MouseInput.BUTTON_LEFT);
        flyCam.setMoveSpeed(80);
        scenario = new MithrilWingsScenarioController("Scene1");
        start = (Node) scenario.getNode().getChild("start");
        ShipController.start = start.getLocalTranslation();
        logic.setScenario(scenario);
        logic.setSky("Sky");
//        ship1 = new ShipController("Ship1");
//        ship1.createCharacterControl();
//        ship1.createGhostControl();
//        logic.addActor(ship1);
//        ship1.setLocalTranslation(start.getLocalTranslation());
        getCamera().setLocation(start.getLocalTranslation());
//        getCamera().lookAt(ship1.getNode().getLocalTranslation(), Vector3f.UNIT_Y);
        dl = new DirectionalLight();
        rootNode.addLight(dl);

        /**
         * Creating the water.. lag?
         */
//        createWater();
        createCubeScene();
        new Thread(logic).start();
        stone_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");


//        log4j.info("Starting client");
//        client = new MithrilWingsClient(reporter);
//        client = new MithrilWingsClient(reporter,this);
//        client.start();
//            log4j.warn("notready....");
//        while(!client.isReady()){
//            try {
//                Thread.sleep(500);
//            } catch (InterruptedException ex) {
//                continue;
//            }
//        }
//        log4j.debug("REQUEST_USERS");
//        client.send(Constants.REQUEST_USERS);
//        
////        client.send(Constants.ADD_USER);
//
//        log4j.debug("REQUEST_ME");
//        client.send(Constants.REQUEST_ME);
        log4j.debug("Starting client");
        Serializer.registerClass(MithrilMessage.class);
        try {
            log4j.debug("Connecting to " + Constants.SERVER + " : " + Constants.PORT);
            myClient = Network.connectToServer(Constants.SERVER, Constants.PORT);
            myClient.start();
            ClientListener cl = new ClientListener();
            log4j.debug("ClientListener created");
            myClient.addMessageListener(cl, MithrilMessage.class);
            log4j.debug("ClientListener added");


            myClient.send(new MithrilMessage(Constants.ADD_USER));
            log4j.debug("ADD_USER sent");
            myClient.send(new MithrilMessage(Constants.REQUEST_USERS));
            log4j.debug("REQUEST_USERS sent");
            myClient.send(new MithrilMessage(Constants.REQUEST_ME));
            log4j.debug("REQUEST_ME sent");
        } catch (IOException ex) {
            ex.printStackTrace();
        }
//            Message message = new ClientMessage("Hello World!");
//            myClient.send(message);
    }
    Material stone_mat;

    @Override
    public void destroy() {
        super.destroy();
        log4j.debug("Destroying app..");
        System.exit(0);
    }
//    CameraNode camNode;

    private void createWater() {
        //create processor
        SimpleWaterProcessor waterProcessor = new SimpleWaterProcessor(assetManager);
        waterProcessor.setReflectionScene(rootNode);
        viewPort.addProcessor(waterProcessor);
        //load plain and set water texture
        Spatial waterPlane = (Spatial) assetManager.loadModel("Models/WaterTest/WaterTest.j3o");
        waterPlane.setMaterial(waterProcessor.getMaterial());
        waterPlane.setLocalScale(300);
        waterPlane.setLocalTranslation(0, -1, 0);
        rootNode.attachChild(waterPlane);
//        waterProcessor.setDebug(true);
    }
    DirectionalLight dl;

    @Override
    public void onAction(String name, boolean isPressed, float tpf) {
        if (me == null) {
            return; // not initialized yet
        }
        super.onAction(name, isPressed, tpf);
        if ("click".equals(name) && isPressed) {
            /**
             * Create a cannon ball geometry and attach to scene graph.
             */
            Geometry ball_geo = new Geometry("cannon ball", sphere);
            ball_geo.setMaterial(stone_mat);
            Node bulletNode = new Node();
            bulletNode.attachChild(ball_geo);
            MyBullet bulletActor = new MyBullet(bulletNode, me.getDirection());
            getLogic().addEntity(bulletActor);
            /**
             * Position the cannon ball
             */
            ball_geo.setLocalTranslation(Common.getInstance().getCamera().getLocation());
        }
    }

    private void changeSky() {
//        log4j.debug("CLICK");
        String skyName = logic.getSky().getName();
        log4j.debug("Changing sky");
        String newSkyName;
        if (skyName.contains("Dark")) {
            newSkyName = "Sky";
        } else {
            newSkyName = "Dark";
        }
//        scenario.getNode().setLocalScale(3f);
//        ((TerrainQuad) scenario.getNode().getChild("terrain")).setLocalTranslation(0, -100, 0);
//            Node start = (Node) scenario.getNode().getChild("start");
//        scenario.getNode().detachChildNamed("Sky");
        logic.setSky(newSkyName);
    }

    @Override
    public void updateFApp(float tpf) {

        try {
            parseQueue();
        } catch (Exception e) {
            e.printStackTrace();
        }

        dl.setDirection(getCamera().getDirection());

        if (me == null) {
            return; // me wasn't initialized yet.
        }
        Vector3f targetPos = me.getNode().getLocalTranslation();
        Vector3f targetDir = me.getDirection();
        Vector3f camPos = targetPos.add(targetDir.negate().mult(10)).add(behind);
        getCamera().setLocation(camPos);

//        client.send(me.getLocationRotation());
        myClient.send(new MithrilMessage(me.getLocationRotation()));
        log4j.debug("me.getLocationRotation() sent :  " + me.getLocationRotation());

    }
    ArrayList<String> queue = new ArrayList<String>();
    boolean queueInUse = false;
    String usingQueue = "";

    private void parseQueue() {
        while (queueInUse) {
            //wait until queue is free
            log4j.warn("Queue is in use by " + usingQueue);
        }

        queueInUse = true;
        usingQueue = "parsingQueue";
        log4j.debug("parseQueue");
        for (String message : queue) {
            log4j.debug(message); // debug
            StringTokenizer tokenizer = new StringTokenizer(message);
            int firstArg = Integer.parseInt(tokenizer.nextToken());
            int id;
            switch (firstArg) {
                case Constants.UPDATE_LOCATION_ROTATION:
//                    if(!clientReady) break;
//                    log4j.debug("Updating Location/Rotation");

                    float lx = Float.parseFloat(tokenizer.nextToken());
                    float ly = Float.parseFloat(tokenizer.nextToken());
                    float lz = Float.parseFloat(tokenizer.nextToken());

                    float rx = Float.parseFloat(tokenizer.nextToken());
                    float ry = Float.parseFloat(tokenizer.nextToken());
                    float rz = Float.parseFloat(tokenizer.nextToken());

                    id = Integer.parseInt(tokenizer.nextToken());

                    Vector3f location = new Vector3f(lx, ly, lz);
                    Vector3f rotation = new Vector3f(rx, ry, rz);

                    ShipController actor = (ShipController) logic.getActor(id);

                    actor.setLocationRotation(location, rotation);

                    break;

                case Constants.ADD_USER:
                    log4j.debug("Adding user ");
//                    Constants.ADD_USER + " " + ch.getId() + " " + ch.getNickname()
                    id = Integer.parseInt(tokenizer.nextToken());
                    String nickname = tokenizer.nextToken();
                    ShipController ship = new ShipController(nickname);
                    ship.createCharacterControl();
                    logic.addActor(id, ship);
                    break;
                case Constants.REQUEST_ME:
//                    clientReady = true;
                    log4j.debug("Requesting me");
                    id = Integer.parseInt(tokenizer.nextToken());
                    ShipController actorMe = (ShipController) logic.getActor(id);
                    if (actorMe != null) {
                        log4j.warn(actorMe.getName());
                        MainMithrilWings.me = actorMe;
                        MainMithrilWings.me.createGhostControl();
                        MainMithrilWings.me.resetPosition();
                    }
                    break;

                case Constants.REMOVE_USER:
//                    Constants.ADD_USER + " " + ch.getId() + " " + ch.getNickname()
                    id = Integer.parseInt(tokenizer.nextToken());
                    log4j.info("Removing user " + id);
                    logic.removeActor(id);
                    break;
                default:
                    log4j.error("RECEIVED A NON PARSABLE ARGUMENT. " + message);
                    break;
            }
        }

        queue.clear();

        log4j.debug("Cleaning Queue");

        queueInUse = false;
        usingQueue = "N/A";
    }

    private synchronized void addToQueue(String command) {
        while (queueInUse) {
            //wait until queue is free
            log4j.warn("Queue is in use by " + usingQueue);
        }
        log4j.debug("addToQueue . " + command);
        queueInUse = true;
        usingQueue = "addToQueue";
        queue.add(command);
        queueInUse = false;
        usingQueue = "N/A";
    }
    Vector3f behind = new Vector3f(0, 3, 0);
    private static final Sphere sphere;

    private void createCubeScene() {
//        log4j.debug("Created Cube Scene");
        Node cubescene = Common.getInstance().getNode("Models/blenderscene/cubescene.j3o");
        cubescene.setLocalTranslation(start.getLocalTranslation().add(Vector3f.UNIT_Y.mult(25)));
        cubescene.setLocalScale(10);
        RigidBodyControl rigidBodyControl = new RigidBodyControl(CollisionShapeFactory.createMeshShape(cubescene), 0);
        cubescene.addControl(rigidBodyControl);
        Common.getInstance().getBulletAppState().getPhysicsSpace().add(rigidBodyControl);
        rootNode.attachChild(cubescene);
    }

    private class MyBullet extends FController implements Runnable {

        Vector3f bulletDirection;
        Node bulletNode;
        boolean readyToDie = false;

        public MyBullet(Node node, Vector3f direction) {
            this.bulletDirection = direction;
            this.bulletNode = node;

            FModel myModel = new FModel("Bullet" + (++bulletCount)) {
            };
            setView(new FView(myModel, this.bulletNode) {
            });
            setModel(myModel);
        }

        @Override
        @SuppressWarnings("SleepWhileInLoop")
        public void run() {
            Date start = new Date();
            while (true) {
                try {
                    Date now = new Date();
                    if (now.getTime() - start.getTime() > 3000l) {
                        break;
                    }
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                    log4j.error("Error in Bullet life loop", ex);
                    continue;
                }
            }
            readyToDie = true;
//                Common.getInstance().getRootNode().detachChild(spatial);
        }
        int speed = 40;

        @Override
        public void update() {
            bulletNode.move(bulletDirection.mult(speed));
            if (readyToDie) {
//                log4j.debug("Detttaching..");
                getLogic().removeEntity(this);
            }
//            if(speed<50) speed = 50;
        }

        @Override
        public boolean isAlive() {
            throw new UnsupportedOperationException("Not supported yet.");
        }
    }
    static int bulletCount = 0;

    static {
        /**
         * Initialize the cannon ball geometry
         */
        sphere = new Sphere(32, 32, 0.4f, true, false);
        sphere.setTextureMode(Sphere.TextureMode.Projected);
    }

    class ClientListener implements MessageListener<Client> {

        static final long serialVersionUID = 1L;

        public ClientListener() {
            log4j.debug("Creating ClientListener");
        }

        public void messageReceived(Client source, Message message) {
            if (message instanceof MithrilMessage) {
                // do something with the message
//            String msg helloMessage = ((ClientMessage) message).getMessage();
                messageReceived(source, ((MithrilMessage) message).getMessage());
            } // else...
        }

        public void messageReceived(Client source, String message) {
            log4j.debug("messageReceived " + message);
            addToQueue(message);
        }
    }
}
